package com.pansoft.openplanet.util;

import android.content.Context;

import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.LegacyAddress;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionInput;
import org.bitcoinj.core.TransactionOutput;
import org.bitcoinj.core.Utils;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.crypto.TransactionSignature;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptBuilder;
import org.bitcoinj.script.ScriptChunk;
import org.bitcoinj.script.ScriptException;
import org.bitcoinj.signers.LocalTransactionSigner;
import org.bitcoinj.signers.TransactionSigner;
import org.bitcoinj.wallet.KeyBag;
import org.bitcoinj.wallet.RedeemData;
import org.bitcoinj.wallet.SendRequest;
import org.bitcoinj.wallet.Wallet;

import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.util.EnumSet;

import javax.annotation.Nullable;

import static com.google.common.base.Preconditions.checkNotNull;
import static org.bitcoinj.script.ScriptOpCodes.getOpCodeName;

public class BtcTransactionManager1 {

    private static final String TAG = "BtcTransactionManager";
    private Context mContext;

    public BtcTransactionManager1(Context mContext) {
        this.mContext = mContext;
    }


    public static final BigInteger HIGH_S_DIFFERENCE = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16);

    public static void main(final String[] argv) throws NoSuchAlgorithmException, IOException {
        try {
            String path ="C:\\Users\\YQS\\Desktop\\工作\\星际资产\\BTC--2018-07-03T20-20-55.749--1LWQYaUwejuvuwfuSm6e7n8t4yvzmCctTQ.wallet";
            initTest(path);
            //parAddress();
            //test1(path);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void test1(String keystorePath) throws Exception {
        org.bitcoinj.core.Context context = org.bitcoinj.core.Context.getOrCreate(MainNetParams.get());
        SendRequest request = SendRequest.to(LegacyAddress.fromBase58(MainNetParams.get(),"1BRN8XmbHaRxkCynQS2QywrAbQHL2NR5rW"), Coin.ZERO);
// The SendRequest object can be customized at this point to modify how the transaction will be created.
        Wallet wallet = Wallet.loadFromFile(new File(keystorePath));
        wallet.completeTx(request);
// Ensure these funds won't be spent again.
        wallet.commitTx(request.tx);

        System.out.println(Utils.HEX.encode(request.tx.unsafeBitcoinSerialize()));

//        wallet.saveToFile(...);
//// A proposed transaction is now sitting in request.tx - send it in the background.
//        ListenableFuture<Transaction> future = peerGroup.broadcastTransaction(request.tx);
    }

    public  static  void parAddress(){
        LegacyAddress legacyAddress =  LegacyAddress.fromBase58(MainNetParams.get(),"1BRN8XmbHaRxkCynQS2QywrAbQHL2NR5rW");
        System.out.println(legacyAddress.toString());

    }


    public static void initTest(String keystorePath) throws Exception {
        final NetworkParameters params = new MainNetParams();
        final LocalTransactionSigner signer = new LocalTransactionSigner();
//        final SecureRandom secureRandom = new SecureRandom();
//        final ECKey key = new ECKey(secureRandom);

        Wallet wallet = Wallet.loadFromFile(new File(keystorePath));
        // 钱包地址 1LWQYaUwejuvuwfuSm6e7n8t4yvzmCctTQ
        System.out.println(wallet.toString());
        DeterministicKey deterministicKey = wallet.currentReceiveKey();
        BigInteger privKeyBTC = deterministicKey.getPrivKey();
        //生成eckey
        final ECKey ecKey = ECKey.fromPrivate(privKeyBTC);
        //私钥
        //String privateKey = ecKey.getPrivateKeyAsWiF(MainNetParams.get()).toString();


//        final KeyBag bag = new KeyBag() {
//            @Override
//            public ECKey findKeyFromPubHash(byte[] pubkeyHash) {
//                return ecKey;
//            }
//
//            @Override
//            public ECKey findKeyFromPubKey(byte[] pubkey) {
//                return ecKey;
//            }
//
//            @Override
//            public RedeemData findRedeemDataFromScriptHash(byte[] scriptHash) {
//                return null;
//            }
//
//        };
        final KeyBag bag = new KeyBag() {
            @Nullable
            @Override
            public ECKey findKeyFromPubKeyHash(byte[] bytes, @Nullable Script.ScriptType scriptType) {
                return ecKey;
            }

            @Nullable
            @Override
            public ECKey findKeyFromPubKey(byte[] bytes) {
                return ecKey;
            }

            @Nullable
            @Override
            public RedeemData findRedeemDataFromScriptHash(byte[] bytes) {
                return null;
            }
        };

        // Generate a fictional output transaction we take values from, and
        // an input transaction for the test case

        final Transaction outputTransaction = new Transaction(params);
        final Transaction inputTransaction = new Transaction(params);

        //输出-转给别人
//        final TransactionOutput output = new TransactionOutput(params, inputTransaction, Coin.ZERO, LegacyAddress.fromKey(params, key));
        final TransactionOutput output = new TransactionOutput(params, inputTransaction, Coin.valueOf(10), LegacyAddress.fromKey(params, ecKey));
        //设置收入 支出
        inputTransaction.addOutput(output);
        outputTransaction.addInput(output);
        //outputTransaction.addOutput(Coin.ZERO, LegacyAddress.fromKey(params, new ECKey(secureRandom)));

        //设置支出给某人
        outputTransaction.addOutput(Coin.valueOf(1), Address.fromString(params,"1BRN8XmbHaRxkCynQS2QywrAbQHL2NR5rW"));



        addOutputs(outputTransaction, bag);

        // Sign the transaction
        final TransactionSigner.ProposedTransaction proposedTransaction = new TransactionSigner.ProposedTransaction(outputTransaction);
        signer.signInputs(proposedTransaction, bag);
        final TransactionInput input = proposedTransaction.partialTx.getInput(0);

        input.verify(output);
        input.getScriptSig().correctlySpends(outputTransaction, 0, output.getScriptPubKey(),
                EnumSet.of(Script.VerifyFlag.DERSIG, Script.VerifyFlag.P2SH));

        final Script scriptSig = input.getScriptSig();
        final TransactionSignature signature = TransactionSignature.decodeFromBitcoin(scriptSig.getChunks().get(0).data, true, false);

        // First output a conventional low-S transaction with the LOW_S flag, for the tx_valid.json set
        System.out.println("[\"A transaction with a low-S signature.\"],");
        System.out.println("[[[\""
                + inputTransaction.getHashAsString() + "\", "
                + output.getIndex() + ", \""
                + scriptToString(output.getScriptPubKey()) + "\"]],\n"
                + "\"" + Utils.HEX.encode(proposedTransaction.partialTx.unsafeBitcoinSerialize()) + "\", \""
                + Script.VerifyFlag.P2SH.name() + "," + Script.VerifyFlag.LOW_S.name() + "\"],");

        final BigInteger highS = HIGH_S_DIFFERENCE.subtract(signature.s);
        final TransactionSignature highSig = new TransactionSignature(signature.r, highS);
        input.setScriptSig(new ScriptBuilder().data(highSig.encodeToBitcoin()).data(scriptSig.getChunks().get(1).data).build());
        input.getScriptSig().correctlySpends(outputTransaction, 0, output.getScriptPubKey(),
                EnumSet.of(Script.VerifyFlag.P2SH));

        // A high-S transaction without the LOW_S flag, for the tx_valid.json set
        System.out.println("[\"A transaction with a high-S signature.\"],");
        System.out.println("[[[\""
                + inputTransaction.getHashAsString() + "\", "
                + output.getIndex() + ", \""
                + scriptToString(output.getScriptPubKey()) + "\"]],\n"
                + "\"" + Utils.HEX.encode(proposedTransaction.partialTx.unsafeBitcoinSerialize()) + "\", \""
                + Script.VerifyFlag.P2SH.name() + "\"],");

        // Lastly a conventional high-S transaction with the LOW_S flag, for the tx_invalid.json set
        System.out.println("[\"A transaction with a high-S signature.\"],");
        System.out.println("[[[\""
                + inputTransaction.getHashAsString() + "\", "
                + output.getIndex() + ", \""
                + scriptToString(output.getScriptPubKey()) + "\"]],\n"
                + "\"" + Utils.HEX.encode(proposedTransaction.partialTx.unsafeBitcoinSerialize()) + "\", \""
                + Script.VerifyFlag.P2SH.name() + "," + Script.VerifyFlag.LOW_S.name() + "\"],");
    }

    private static void addOutputs(final Transaction outputTransaction, final KeyBag bag) throws ScriptException {
        int numInputs = outputTransaction.getInputs().size();
        for (int i = 0; i < numInputs; i++) {
            TransactionInput txIn = outputTransaction.getInput(i);
            Script scriptPubKey = txIn.getConnectedOutput().getScriptPubKey();
            RedeemData redeemData = txIn.getConnectedRedeemData(bag);
            checkNotNull(redeemData, "Transaction exists in wallet that we cannot redeem: %s", txIn.getOutpoint().getHash());
            txIn.setScriptSig(scriptPubKey.createEmptyInputScript(redeemData.keys.get(0), redeemData.redeemScript));
        }
    }

    /**
     *
     * 将脚本转换为适合所需样式的字符串格式
     * Convert a script to a string format that suits the style expected in
     * tx_valid.json and tx_invalid.json.
     */
    private static String scriptToString(Script scriptPubKey) {
        final StringBuilder buf = new StringBuilder();
        for (ScriptChunk chunk : scriptPubKey.getChunks()) {
            if (buf.length() > 0) {
                buf.append(" ");
            }
            if (chunk.isOpCode()) {
                buf.append(getOpCodeName(chunk.opcode));
            } else if (chunk.data != null) {
                // Data chunk
                buf.append("0x")
                        .append(Integer.toString(chunk.opcode, 16)).append(" 0x")
                        .append(Utils.HEX.encode(chunk.data));
            } else {
                buf.append(chunk.toString());
            }
        }
        return buf.toString();
    }
}
